#include "dswboardcast.h"

#define MAX_UDP_PACKET_LEN 1024

#define CLEAR_DEV_HASH_TABLE(x){LPST_EXTERNAL_CAM_DSW_T pNode; while ((pNode = (LPST_EXTERNAL_CAM_DSW_T)x->RemoveNext()) != NULL) {delete pNode;};}

CDswboardcast::CDswboardcast(LPST_ENV_T pEnv)
: m_pEnv(pEnv)
, m_BoardCast_socket(-1)
, m_nThreadId(-1)
, m_bDescover(false)
{
	LOG4CPLUS_DEBUG(log_dsw, "Create Boardcast!");
	pthread_mutex_init(&m_nTableLock, NULL);
	m_pDevTable = HashTable::create(STRING_HASH_KEYS);
	pthread_create(&m_nThreadId, NULL, CDswboardcast::cbMultiSocketThread, this);
	m_nTimer.ev = evtimer_new(m_pEnv->pEvBase, CDswboardcast::cbTimerTask, this);
	m_nTimer.tv.tv_sec = 1;
	m_nTimer.tv.tv_usec = 0;
	evtimer_add(m_nTimer.ev, &m_nTimer.tv);
}

CDswboardcast::~CDswboardcast()
{
	CLEAR_EV(m_nTimer);
	if (m_nThreadId != -1)
		pthread_join(m_nThreadId, NULL);
	if (m_BoardCast_socket != -1)
		close(m_BoardCast_socket);
	pthread_mutex_destroy(&m_nTableLock);
	CLEAR_DEV_HASH_TABLE(m_pDevTable);
	delete m_pDevTable;
	LOG4CPLUS_DEBUG(log_dsw, "Destroy Boardcast!");
}

void CDswboardcast::MultiSocketThread()
{
RESTART_THREAD:
	LOG4CPLUS_DEBUG(log_dsw, "Start MultiSocket thread!");
	int sockfd = -1;
	struct sockaddr_in server, client;
	socklen_t sin_size;
	bool bEndThread = false;
	char* pMsg = new char[1024 * 2];
	struct ip_mreq mreq;
	int optval = 1;
	char sIp[64] = {0};
	void* pParam;

	do{
WAIT_NETWORK_READY:
		if (m_pEnv->bExit)
			break;
		if (!m_pEnv->bNetworkReady){
			usleep(1000 * 100);
			goto WAIT_NETWORK_READY;
		}
		pParam = (void*)sIp;
		m_pEnv->pOnNotifyFunc(PMSG_GET_ACTIVEIP, pParam);
		if (strlen(sIp) <= 0){
			usleep(1000 * 100);
			goto WAIT_NETWORK_READY;
		}
		if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
			LOG4CPLUS_ERROR(log_dsw, "Creating socket failed!");
			break;
		}
		bzero(&server, sizeof(server));
		server.sin_family = AF_INET;
		server.sin_port = htons(defMultiCastCmdPort);
		server.sin_addr.s_addr = htonl(INADDR_ANY);
		if (bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1){
			LOG4CPLUS_ERROR(log_dsw, "bind error! err = " << errno);
			break;
		}
		mreq.imr_multiaddr.s_addr = inet_addr(defMultiCastCmdIp);
		mreq.imr_interface.s_addr = inet_addr(sIp);
		if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq)) < 0) {
			LOG4CPLUS_ERROR(log_dsw, "setsockopt IP_ADD_MEMBERSHIP error! err = " << errno);
			break;
		}
		if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof(int))<0){
			LOG4CPLUS_ERROR(log_dsw, "setsockopt SO_REUSEADDR error! err = " << errno);
			break;
		}
		while(!m_pEnv->bExit && !bEndThread){
			fd_set readfd;
			struct timeval timeout = {0, 1000 * 100};
			FD_ZERO(&readfd);
			FD_SET(sockfd, &readfd);
			switch(select(sockfd + 1, &readfd, NULL, NULL, &timeout)){
			case -1:
				if (errno != 4){
					LOG4CPLUS_ERROR(log_dsw, "Broadcast select error! err = " << errno);
					bEndThread = true;
				}
				break;
			case 0:
				break;
			default:
				if (FD_ISSET(sockfd, &readfd)){
					int num = recvfrom(sockfd, pMsg, MAX_UDP_PACKET_LEN, 0, (struct sockaddr *)&client, &sin_size);
					if (num < 0){
						LOG4CPLUS_ERROR(log_dsw, "recvfrom error! err = " << errno);
						break;
					}
					if (client.sin_addr.s_addr == 0)
						break;
					pMsg[num] = '\0';
					ProcessBroadcastMsg(pMsg);
				}
				break;
			}
		}
	}while(0);
	if (sockfd != -1)
		close(sockfd);
	delete pMsg;
	LOG4CPLUS_DEBUG(log_dsw, "Exit MultiSocket thread!");
	if (bEndThread){
		bEndThread = false;
		goto RESTART_THREAD;
	}
}

void CDswboardcast::ProcessBroadcastMsg(char* pMsg)
{
	char* pBroadcastMsg = pMsg + PAYLOAD_OFFSET;
	CMarkup xml;
	char sPUAID[64], sPUAIP[64];
	int nPUATYPE;
	int nPUAVPORT;
	bool bNew = false;

	if (!xml.SetDoc(pBroadcastMsg))
		return;
	if (!xml.FindElem("Message"))
		return;
	xml.IntoElem();
	if (!xml.FindElem("DSW_HEADER"))
		return;
	xml.IntoElem();
	if (!xml.FindElem("MessageType"))
		return;
	if (strcmp(xml.GetData().c_str(), "MSG_PUA_REGIST") != 0)
		return;
	xml.OutOfElem();
	if (!xml.FindElem("DSW_PUA_ID"))
		return;
	strcpy(sPUAID, xml.GetData().c_str());
	if (!xml.FindElem("PUA_TYPE"))
		return;
	nPUATYPE = atoi(xml.GetData().c_str());
	if (((nPUATYPE & 0x04) != 0x04) &&
		((nPUATYPE & 0x06) != 0x06))
		return;
	if (!xml.FindElem("DSW_PUA_IP"))
		return;
	strcpy(sPUAIP, xml.GetData().c_str());
	if (!xml.FindElem("DSW_VPORT"))
		return;
	nPUAVPORT = atoi(xml.GetData().c_str());

	pthread_mutex_lock(&m_nTableLock);
	if (m_bDescover){
		LPST_EXTERNAL_CAM_DSW_T pNode = (LPST_EXTERNAL_CAM_DSW_T)m_pDevTable->Lookup(sPUAID);
		if (pNode == NULL){
			pNode = new ST_EXTERNAL_CAM_DSW_T;
			bNew = true;
		}
		strcpy(pNode->sPUAID, sPUAID);
		strcpy(pNode->sIp, sPUAIP);
		pNode->nVport = nPUAVPORT;
		pNode->nLastTm = time(NULL);
		if (bNew){
			m_pDevTable->Add(pNode->sPUAID, pNode);
			LOG4CPLUS_DEBUG(log_dsw, "Find new dsw device! " << pNode->sPUAID << " [ " << pNode->sIp << ":" << pNode->nVport << " ]");
		}
	}
	pthread_mutex_unlock(&m_nTableLock);
}

void CDswboardcast::StartDescover()
{
	pthread_mutex_lock(&m_nTableLock);
	m_bDescover = true;
	CLEAR_DEV_HASH_TABLE(m_pDevTable);
	pthread_mutex_unlock(&m_nTableLock);
}

void CDswboardcast::StopDescover()
{
	pthread_mutex_lock(&m_nTableLock);
	m_bDescover = false;
	pthread_mutex_unlock(&m_nTableLock);
}

int CDswboardcast::GetDescoverResult(CMarkup* pXml)
{
	int nRet = -1;
	LPST_EXTERNAL_CAM_DSW_T pNode;
	char const* key = NULL;
	pthread_mutex_lock(&m_nTableLock);
	if (m_pDevTable != NULL){
		nRet = 0;
		HashTable::Iterator* iter = HashTable::Iterator::create(*m_pDevTable);
		pXml->SetDoc("");
		pXml->AddElem("EXT_CAM");
		pXml->IntoElem();
		pXml->AddElem("CAMS", m_pDevTable->numEntries());
		while((pNode = (LPST_EXTERNAL_CAM_DSW_T)iter->next(key)) != NULL){
			if (abs(pNode->nLastTm - time(NULL)) < 30){
				pXml->AddElem("CAM");
				pXml->IntoElem();
				pXml->AddElem("VALID", 0);
				pXml->AddElem("CAM_ID", pNode->sPUAID);
				char sURL[64];
				sprintf(sURL, "%s:%d", pNode->sIp, pNode->nVport);
				pXml->AddElem("CAM_AD", sURL);
				pXml->OutOfElem();
				nRet++;
			}
		}
		pXml->AddElem("S_STATUS", (m_bDescover == false) ? 2 : 3);
		pXml->OutOfElem();
		pXml->ResetPos();
		delete iter;
	}	
	pthread_mutex_unlock(&m_nTableLock);
	return nRet;
}

void CDswboardcast::OnTimer(int fd, short events)
{
	CMarkup xml;
	char sip[64], sbuf[32], sid[32];
	int ch_num, nNum;
	void* pParam;

//	LOG4CPLUS_DEBUG(log_dsw, "Boardcast Task is running!");
	if (!m_pEnv->bNetworkReady)
		goto BoardCastEnd;
	if (m_BoardCast_socket == -1){
		m_BoardCast_socket = socket(AF_INET, SOCK_DGRAM, 0);
		int en_multicast = 1;
		if (setsockopt(m_BoardCast_socket, IPPROTO_IP, IP_MULTICAST_TTL, &en_multicast, sizeof(en_multicast)) < 0){
			LOG4CPLUS_ERROR(log_dsw, "setsocketopt IP_MULTICAST_TTL error!errcode = " << errno);
			close(m_BoardCast_socket);
			m_BoardCast_socket = -1;
			goto BoardCastEnd;
		}
		int loop = 0;
		if(setsockopt(m_BoardCast_socket, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) !=0){
			LOG4CPLUS_ERROR(log_dsw, "Set send socket IP_MULTICAST_LOOP_ERROR, err = " << errno);
			close(m_BoardCast_socket);
			m_BoardCast_socket = -1;
			goto BoardCastEnd;
		}

	}
	
	xml.SetDoc("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
	xml.AddElem("Message");
	xml.AddAttrib("Version", "1.0.0");
	xml.AddChildElem("DSW_HEADER");
	xml.IntoElem();
		xml.AddChildElem("MessageType", "MSG_PUA_REGIST");
	xml.OutOfElem();
	xml.AddChildElem("DSW_KEY", defDSWKEY);
	m_pEnv->pOnNotifyFunc(PMSG_GET_BASIC, pParam);
	xml.AddChildElem("DSW_PUA_ID", ((LPST_DEVICE_BASE)pParam)->devid);
	strcpy(sid, ((LPST_DEVICE_BASE)pParam)->devid);

	pParam = (void*)sid;
	m_pEnv->pOnNotifyFunc(PMSG_GET_DEVTYPE, pParam);
	xml.AddChildElem("PUA_TYPE", *((int*)pParam));

	xml.AddChildElem("DSW_PUA_SN", "");
	pParam = (void*)sip;
	m_pEnv->pOnNotifyFunc(PMSG_GET_ACTIVEIP, pParam);
	if (strlen(sip) > 0){
		xml.AddChildElem("DSW_PUA_IP", sip);
		xml.AddChildElem("DSW_PUA_PORT", 80);
		m_pEnv->pOnNotifyFunc(PMSG_GET_NETINFO, pParam);
		xml.AddChildElem("DSW_PUA_MAC", ((LPST_LAN_PARAM)pParam)->mac);
		m_pEnv->pOnNotifyFunc(PMSG_GET_LOCATE, pParam);
		xml.AddChildElem("DSW_PUA_LOCATE", ((LPST_LOCATION_PARAM)pParam)->dev_location);
		m_pEnv->pOnNotifyFunc(PMSG_GET_ALARMINFO, pParam);
		xml.AddChildElem("DSW_PUA_LOCK", ((LPST_ALARM_PARAM)pParam)->enalarm);
		m_pEnv->pOnNotifyFunc(PMSG_GET_OSVER, pParam);
		xml.AddChildElem("DSW_OSVER", (char*)pParam);
		m_pEnv->pOnNotifyFunc(PMSG_GET_APPVER, pParam);
		xml.AddChildElem("DSW_APPVER", (char*)pParam);
		pParam = (void*)sip;
		m_pEnv->pOnNotifyFunc(PMSG_GET_VIDEOLISTEN_PORT, pParam);
		xml.AddChildElem("DSW_VPORT", *((int*)pParam));
		xml.AddChildElem("DSW_CHN", m_pEnv->nMaxChannel);
		for (ch_num = 0; ch_num < m_pEnv->nMaxChannel; ch_num++){
			sprintf(sbuf, "CH%d", ch_num);
			xml.AddChildElem(sbuf);
			xml.IntoElem();
				xml.AddChildElem("DSW_STREAM", "1;0");
				nNum = ch_num;
				pParam = (void*)&nNum;
				m_pEnv->pOnNotifyFunc(PMSG_GET_VIDEOINFO, pParam);
				xml.AddChildElem("LOCATE", ((LPST_CH_PARAM)pParam)->ch_location);
			xml.OutOfElem();
		}

		if (m_pEnv->nMaxChannel == 4){
			m_pEnv->pOnNotifyFunc(PMSG_GET_EXTERNAL_CAMS, pParam);
			nNum = 0;
			for (ch_num = 0; ch_num < MAX_EXTERNAL_CAMS; ch_num++){
				if ((strlen(((LPST_EXTERNAL_CAMS_T)pParam)->nExtCam[ch_num].sUUID) > 0) &&
					(strcmp(((LPST_EXTERNAL_CAMS_T)pParam)->nExtCam[ch_num].sType, "ONVIF") == 0))
					nNum++;
			}
			xml.AddChildElem("DSW_EXCHN", nNum);
			nNum = 0;
			for (ch_num = 0; ch_num < MAX_EXTERNAL_CAMS; ch_num++){
				if ((strlen(((LPST_EXTERNAL_CAMS_T)pParam)->nExtCam[ch_num].sUUID) > 0) &&
					(strcmp(((LPST_EXTERNAL_CAMS_T)pParam)->nExtCam[ch_num].sType, "ONVIF") == 0)){
					sprintf(sbuf, "EXCH%d", nNum);
					xml.AddChildElem(sbuf);
					xml.IntoElem();
						xml.AddChildElem("UUID", ((LPST_EXTERNAL_CAMS_T)pParam)->nExtCam[ch_num].sUUID);
						ST_EXTERNAL_CAM_PROPERTY_T nExtPropty[2] = {0};
						ST_EXTERNAL_CAM_DETAIL_T nExtDetail;
						void* pParamExt;
						strcpy(nExtDetail.nExtCam.sUUID, ((LPST_EXTERNAL_CAMS_T)pParam)->nExtCam[ch_num].sUUID);
						nExtDetail.nTotalStream = 0;
						nExtDetail.pStreamProperty = nExtPropty;
						pParamExt = (void*)&nExtDetail;
						m_pEnv->pOnNotifyFunc(PMSG_GET_EXTERNAL_CAM_STATUS, pParamExt);
						xml.AddChildElem("ONLINE", nExtDetail.nExtCam.bOnLine);
						if (nExtDetail.nTotalStream >= 2)
							xml.AddChildElem("DSW_STREAM", "1;0");
						else
							xml.AddChildElem("DSW_STREAM", "1");
						xml.AddChildElem("MAIN_STREAM", nExtPropty[0].sStreamURL);
						xml.AddChildElem("SUB_STREAM", nExtPropty[1].sStreamURL);
					xml.OutOfElem();
					nNum++;
				}
			}
		}
		
		xml.AddChildElem("DSW_RESULT");
		xml.IntoElem();
			xml.AddChildElem("Errcode", 0);
		xml.OutOfElem();
	}
	SendOutOnMulti(sid, (char*)xml.GetDoc().c_str());
BoardCastEnd:
	m_nTimer.tv.tv_sec = 5;
	evtimer_add(m_nTimer.ev, &m_nTimer.tv);
}

void CDswboardcast::SendOutOnMulti(char* pDevid, char* pCmdSend)
{
	int nTotalLen = strlen(pCmdSend);
	int nLeftLen = nTotalLen;
	char* pPos = pCmdSend;
	int nsendout = 0;
	int nCurrentID = 0;
	int nTotalID = nTotalLen / MAX_PACKET_LEN + (nTotalLen % MAX_PACKET_LEN ? 1 : 0);
	char scallid[64] = {0};
	char sSendBuf[2048];
	memset(scallid,0x00,sizeof(scallid));
	sprintf(scallid, "%s_%d", pDevid, (int)time(NULL));
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(defMultiCastCmdPort);
	addr.sin_addr.s_addr = inet_addr(defMultiCastCmdIp);
	bzero(&(addr.sin_zero), 8);
	while (nLeftLen > MAX_PACKET_LEN){
		char* pcmd = sSendBuf;
		LPCOM_PACKET_T pPacket = (LPCOM_PACKET_T)pcmd;
		pPacket->pdata = pcmd + (FLAG_DATA_SIZE - 1);	//payload pos = start - 1 - 64 - 1 - 2
		pPacket->start = 0x00;	//start 0x00
		sprintf(pPacket->callid, "%s", scallid);	//callid
		pPacket->packet_flag = (char)(nTotalID << 4 | nCurrentID);	//packet flag
		nCurrentID++;
		pPacket->size = MAX_PACKET_LEN;	//size
		memcpy(pPacket->pdata, pPos, MAX_PACKET_LEN);	//pdata
		pPos += MAX_PACKET_LEN;
		pcmd[MAX_PACKET_LEN + FLAG_DATA_SIZE - 1] = 0x00;	//end flag
		nLeftLen -= MAX_PACKET_LEN;
		nsendout = sendto(m_BoardCast_socket, pPacket, pPacket->size + FLAG_DATA_SIZE, 0, (const sockaddr*)&addr, sizeof(addr));
		if(nsendout!=pPacket->size + FLAG_DATA_SIZE)
		{
		}
	}
	if (nLeftLen > 0){
		char* pcmd = sSendBuf;
		LPCOM_PACKET_T pPacket = (LPCOM_PACKET_T)pcmd;
		pPacket->pdata = pcmd + (FLAG_DATA_SIZE - 1);	//payload pos = start - 1 - 64 - 1 - 2
		pPacket->start = 0x00;	//start 0x00

		memset(pPacket->callid, 0x00, sizeof(pPacket->callid));

		sprintf(pPacket->callid, "%s", scallid);	//callid
		pPacket->packet_flag = (char)(nTotalID << 4 | nCurrentID);	//packet flag
		pPacket->size = nLeftLen;	//size
		memcpy(pPacket->pdata, pPos, nLeftLen);	//pdata
		pcmd[nLeftLen + FLAG_DATA_SIZE - 1] = 0x00;	//end flag
		nsendout = sendto(m_BoardCast_socket, pPacket, pPacket->size + FLAG_DATA_SIZE, 0, (const sockaddr*)&addr, sizeof(addr));
		if(nsendout!=pPacket->size + FLAG_DATA_SIZE)
		{
		}
	}
}

